<template>
  <div :style="styles.layout">
    <AssistantAside @assistantInfo="onAssistantInfo" />
    <div v-loading="sessionLoading" :style="styles.menu">
      <!-- 🌟 Logo -->
      <div :style="styles.logo">
        <img
          :src="originalIcon"
          draggable="false"
          alt="logo"
          :style="styles['logo-img']"
        />
        <span :style="styles['logo-span']">聊天</span>
      </div>

      <!-- 🌟 添加会话 -->
      <Button type="link" :style="styles.addBtn" @click="onAddConversation">
        <PlusOutlined />
        新建会话
      </Button>

      <!-- 🌟 会话管理 -->
      <Conversations
        :menu="menuConfig"
        :items="conversationsItems"
        :style="styles.conversations"
        :active-key="sessionKey"
        @active-change="onConversationClick"
      />
    </div>

    <div v-loading="chatLoading" :style="styles.chat">
      <!-- 🌟 消息列表 -->
      <Bubble.List :items="items" :roles="roles" :style="styles.messages">
      </Bubble.List>

      <!-- 🌟 提示词 -->
      <!-- <Prompts :items="senderPromptsItems" @item-click="onPromptsItemClick" /> -->

      <!-- 🌟 输入框 -->
      <Sender
        :value="content"
        :style="styles.sender"
        :loading="agentRequestLoading"
        :disabled="disabled"
        @submit="onSubmit"
        @change="(value) => (content = value)"
      >
        <template #prefix>
          <Badge :dot="attachedFiles.length > 0 && !headerOpen">
            <Button type="text" @click="() => (headerOpen = !headerOpen)">
              <template #icon>
                <PaperClipOutlined />
              </template>
            </Button>
          </Badge>
        </template>

        <template #header>
          <Sender.Header
            title="Attachments"
            :open="headerOpen"
            :styles="{ content: { padding: 0 } }"
            @open-change="(open) => (headerOpen = open)"
          >
            <Attachments
              :before-upload="() => false"
              :items="attachedFiles"
              @change="handleFileChange"
            >
              <template #placeholder="type">
                <Flex
                  v-if="type && type.type === 'inline'"
                  align="center"
                  justify="center"
                  vertical
                  gap="2"
                >
                  <Typography.Text style="font-size: 30px; line-height: 1">
                    <CloudUploadOutlined />
                  </Typography.Text>
                  <Typography.Title
                    :level="5"
                    style="margin: 0; font-size: 14px; line-height: 1.5"
                  >
                    Upload files
                  </Typography.Title>
                  <Typography.Text type="secondary">
                    Click or drag files to this area to upload
                  </Typography.Text>
                </Flex>
                <Typography.Text v-if="type && type.type === 'drop'">
                  Drop file here
                </Typography.Text>
              </template>
            </Attachments>
          </Sender.Header>
        </template>
      </Sender>
    </div>
    <!-- 会话重命名 -->
    <el-dialog
      v-model="sessionVisible"
      title="重命名"
      width="500"
      :before-close="handleSessionClose"
    >
      <el-form
        ref="sessionRef"
        :model="sessionForm"
        :rules="sessionRules"
        label-width="80px"
        class="form-box"
      >
        <el-form-item label="名称" prop="name">
          <el-input v-model="sessionForm.name" />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="sessionVisible = false">取消</el-button>
          <el-button type="primary" @click="handleSessionSure(sessionRef)">
            确定
          </el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>

<script setup lang='ts'>
import type {
  AttachmentsProps,
  BubbleListProps,
  ConversationsProps,
  PromptsProps,
  BubbleProps,
} from "ant-design-x-vue";
import type { VNode } from "vue";
import {
  CloudUploadOutlined,
  CommentOutlined,
  EllipsisOutlined,
  FireOutlined,
  HeartOutlined,
  PaperClipOutlined,
  PlusOutlined,
  ReadOutlined,
  ShareAltOutlined,
  SmileOutlined,
  DeleteOutlined,
  EditOutlined,
} from "@ant-design/icons-vue";
import {
  Badge,
  Button,
  Flex,
  Image,
  Space,
  Typography,
  theme,
} from "ant-design-vue";
import {
  Attachments,
  Bubble,
  Conversations,
  Prompts,
  Sender,
  useXAgent,
  useXChat,
  Welcome,
  XStream,
} from "ant-design-x-vue";
import {
  computed,
  h,
  ref,
  reactive,
  watch,
  onMounted,
  onBeforeUnmount,
} from "vue";
import type { FormInstance, FormRules } from "element-plus";
import { ElMessage, ElMessageBox } from "element-plus";
import markdownit from "markdown-it";
import AssistantAside from "./assistantAside.vue";
import {
  sessionListApi,
  sessionCreateApi,
  sessionUpdateApi,
  sessionRemoveApi,
} from "@/api/knowledge/chat.js";
import { getToken } from "@/utils/auth";
import aiIcon from "@/assets/images/ai-icon.webp";
import originalIcon from "@/assets/icons/svg/original.svg";
import userIcon from "@/assets/logo/logo.png";

interface RuleForm {
  name: string;
}

const { token } = theme.useToken();

const styles = computed(() => {
  return {
    layout: {
      width: "100%",
      "min-width": "800px",
      height: `calc(100vh - 84px)`,
      "border-radius": `${token.value.borderRadius}px`,
      display: "flex",
      background: `${token.value.colorBgContainer}`,
      "font-family": `AlibabaPuHuiTi, ${token.value.fontFamily}, sans-serif`,
    },
    menu: {
      background: `${token.value.colorBgLayout}80`,
      width: "280px",
      height: "100%",
      display: "flex",
      "flex-direction": "column",
    },
    conversations: {
      padding: "0 12px",
      flex: 1,
      "overflow-y": "auto",
    },
    chat: {
      flex: 1,
      height: "100%",
      // "max-width": "700px",
      margin: "0 auto",
      "box-sizing": "border-box",
      display: "flex",
      "flex-direction": "column",
      padding: `${token.value.paddingLG}px`,
      gap: "16px",
    },
    messages: {
      flex: 1,
      width: "100%",
    },
    placeholder: {
      "padding-top": "32px",
      "text-align": "left",
      flex: 1,
      width: "100%",
    },
    sender: {
      "box-shadow": token.value.boxShadow,
    },
    logo: {
      display: "flex",
      height: "72px",
      "align-items": "center",
      "justify-content": "start",
      padding: "0 24px",
      "box-sizing": "border-box",
    },
    "logo-img": {
      width: "24px",
      height: "24px",
      display: "inline-block",
    },
    "logo-span": {
      display: "inline-block",
      margin: "0 8px",
      "font-weight": "bold",
      color: token.value.colorText,
      "font-size": "16px",
    },
    addBtn: {
      background: "#1677ff0f",
      border: "1px solid #1677ff34",
      width: "calc(100% - 24px)",
      margin: "0 12px 24px 12px",
    },
  } as const;
});

defineOptions({ name: "PlaygroundIndependentSetup" });

const sleep = () => new Promise((resolve) => setTimeout(resolve, 500));

function renderTitle(icon: VNode, title: string) {
  return h(Space, { align: "start" }, [icon, h("span", title)]);
}

const menuConfig: ConversationsProps["menu"] = (conversation) => ({
  items: [
    {
      label: "重命名",
      key: "resetName",
      icon: h(EditOutlined),
    },
    {
      label: "删除",
      key: "delete",
      icon: h(DeleteOutlined),
      danger: true,
    },
  ],
  onClick: (menuInfo) => {
    sessionId.value = conversation.key;
    if (menuInfo.key === "resetName") {
      sessionForm.name = conversation?.label;
      sessionVisible.value = true;
    } else if (menuInfo.key === "delete") {
      ElMessageBox.confirm("确定删除吗?", "提示", {
        confirmButtonText: "是",
        cancelButtonText: "否",
        type: "warning",
      })
        .then(() => {
          removeSession();
        })
        .catch(() => {
          ElMessage({
            type: "info",
            message: "取消删除",
          });
        });
    }
  },
});

const placeholderPromptsItems: PromptsProps["items"] = [
  {
    key: "1",
    label: renderTitle(
      h(FireOutlined, { style: { color: "#FF4D4F" } }),
      "Hot Topics"
    ),
    description: "What are you interested in?",
    children: [
      {
        key: "1-1",
        description: `What's new in X?`,
      },
      {
        key: "1-2",
        description: `What's AGI?`,
      },
      {
        key: "1-3",
        description: `Where is the doc?`,
      },
    ],
  },
  {
    key: "2",
    label: renderTitle(
      h(ReadOutlined, { style: { color: "#1890FF" } }),
      "Design Guide"
    ),
    description: "How to design a good product?",
    children: [
      {
        key: "2-1",
        icon: h(HeartOutlined),
        description: `Know the well`,
      },
      {
        key: "2-2",
        icon: h(SmileOutlined),
        description: `Set the AI role`,
      },
      {
        key: "2-3",
        icon: h(CommentOutlined),
        description: `Express the feeling`,
      },
    ],
  },
];

const senderPromptsItems: PromptsProps["items"] = [
  {
    key: "1",
    description: "Hot Topics",
    icon: h(FireOutlined, { style: { color: "#FF4D4F" } }),
  },
  {
    key: "2",
    description: "Design Guide",
    icon: h(ReadOutlined, { style: { color: "#1890FF" } }),
  },
];

// ==================== State ====================
let assistantInfo: any = ref({});
const sessionLoading = ref(false);
const chatLoading = ref(false);
const headerOpen = ref(false);
const content = ref("");
const conversationsItems: any = ref([]);
const sessionKey = ref(""); // 会话id
const attachedFiles = ref<AttachmentsProps["items"]>([]);
const agentRequestLoading = ref(false);
const sessionVisible = ref(false);
const sessionRef = ref<FormInstance>();
const sessionForm = reactive<any>({
  name: "",
});
const sessionId = ref("");
const messages: any = ref([]); // 会话内容
const thinkStr = "思考中";
const newSessionStr = "新会话";
const baseUrl = import.meta.env.VITE_APP_BASE_API;
const md = markdownit({ html: true, breaks: true });

let disabled = ref(true); // 提问框禁用
let eventSource: any = null;

let roles: BubbleListProps["roles"] = {
  assistant: {
    placement: "start",
    // typing: { step: 5, interval: 20 },
    styles: {
      content: {
        borderRadius: "16px",
      },
    },
    avatar: {
      icon: h(Image, {
        src: assistantInfo?.value?.avatar
          ? baseUrl + assistantInfo.value.avatar
          : aiIcon,
        width: "auto",
        height: "32px",
        style: { background: "#c0c4cc" },
      }),
    },
  },
  user: {
    placement: "end",
    variant: "shadow",
    avatar: {
      icon: h(Image, {
        src: userIcon,
        width: "auto",
        style: { background: "#c0c4cc" },
      }),
    },
  },
};

const sessionRules = reactive<FormRules<RuleForm>>({
  name: [{ required: true, message: "请输入名称", trigger: "blur" }],
});
// ==================== Runtime ====================
onMounted(() => {});

const [agent] = useXAgent({
  request: async ({ message }, { onSuccess }) => {
    agentRequestLoading.value = true;
    await sleep();
    agentRequestLoading.value = false;
    onSuccess(`Mock success return. You said: ${message}`);
  },
});

const { onRequest } = useXChat({
  agent: agent.value,
});

const renderMarkdown: BubbleProps["messageRender"] = (content) => {
  return h(Typography, null, {
    default: () => h("div", { innerHTML: md.render(content) }),
  });
};

// ==================== API ====================
// 获取助理会话列表
const getSessionList = async (isClick = false) => {
  if (!isClick) {
    sessionLoading.value = true;
  }
  const params = {
    chat_id: assistantInfo.value.id,
  };
  const { data } = await sessionListApi(params);
  if (data && data.length > 0) {
    conversationsItems.value = data.map((item) => {
      return {
        ...item,
        key: item.id,
        label: item.name,
      };
    });
  } else {
    conversationsItems.value = [];
  }
  if (!isClick) {
    sessionLoading.value = false;
  }
};

// 新建助理会话
const createSession = async (name) => {
  const params = {
    chat_id: assistantInfo.value.id,
    name,
  };
  const { code, data } = await sessionCreateApi(params);
  if (code === 200) {
    sessionKey.value = data?.id;
    connectSSE(name);
    getSessionList();
  }
};

// 更新助理会话
const updateSession = async (session_id, name) => {
  const params = {
    chat_id: assistantInfo.value.id,
    session_id,
    name,
  };
  const { code } = await sessionUpdateApi(params);
  if (code === 200) {
    getSessionList();
    sessionVisible.value = false;
  }
};

// 删除助理会话
const removeSession = async () => {
  const params = {
    chat_id: assistantInfo.value.id,
    ids: [sessionId.value],
  };
  const { code } = await sessionRemoveApi(params);
  if (code === 200) {
    ElMessage({
      type: "success",
      message: "删除成功",
    });
    getSessionList();
  }
};

watch(
  sessionKey,
  (val) => {
    if (sessionKey.value !== undefined) {
      disabled.value = false;
      chatLoading.value = true;
      // 如果不是新建
      if (val !== newSessionStr) {
        // messages.value = [];
        let findItem = conversationsItems.value.find(
          (item) => item.key === val
        );
        if (findItem) {
          messages.value = messages.value.concat(
            findItem?.messages.map((item) => {
              if (!item.content) {
                item.content = thinkStr;
              }
              return item;
            })
          );
        }
      } else {
        messages.value = [
          {
            role: "assistant",
            content: assistantInfo.value.prologue || "你好，我是你的聊天助手",
          },
        ];
      }
      setTimeout(() => {
        chatLoading.value = false;
      }, 500);
    }
  },
  { immediate: true }
);

// ==================== Event ====================
function onAssistantInfo(data) {
  assistantInfo.value = Object.assign({}, data);
  messages.value = [];
  disabled.value = true;
  if (roles) {
    roles.assistant.avatar.icon = h(Image, {
      src: assistantInfo?.value?.avatar
        ? baseUrl + assistantInfo.value.avatar
        : aiIcon,
      width: "auto",
      height: "32px",
      style: { background: "#c0c4cc" },
    });
  }
  getSessionList();
}

function handleSessionClose() {
  sessionVisible.value = false;
}

async function handleSessionSure(formEl: FormInstance | undefined) {
  if (!formEl) return;
  await formEl.validate((valid, fields) => {
    if (valid) {
      updateSession(sessionId.value, sessionForm.name);
    } else {
      console.log("error submit!", fields);
    }
  });
}

function replaceThinkToSection(text: string = "") {
  const pattern = /<think>([\s\S]*?)<\/think>/g;

  const result = text.replace(pattern, '<section class="think">$1</section>');

  console.log(result, "思考----");
  return result;
}

const showError = (msg, aiMsg) => {
  ElMessage({
    type: "error",
    message: msg,
  });
  aiMsg.content = msg;
  messages.value.splice(-1, 1, aiMsg);
  agentRequestLoading.value = false;
};

// 连接 SSE
const connectSSE = async (question) => {
  const userMsg = { role: "user", content: question };
  messages.value.push(userMsg);

  const aiMsg = { role: "assistant", content: "" };

  messages.value.push({
    role: "assistant",
    content: thinkStr,
  });

  try {
    agentRequestLoading.value = true;
    await sleep();

    const response: any = await fetch(
      `${baseUrl}ai/dataset/session/charts/completions`,
      {
        method: "POST",
        headers: {
          Authorization: "Bearer " + getToken(),
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          chat_id: assistantInfo.value.id,
          session_id: sessionKey.value,
          question,
          stream: true,
        }), // POST 参数
      }
    );

    for await (const chunk of XStream({
      readableStream: response.body,
    })) {
      if (!chunk.data) {
        const errorStr = "消息格式错误";
        showError(errorStr, aiMsg);
        return;
      }

      const value = JSON.parse(chunk.data);

      // if (value.data == true) {
      //   console.log("完成");
      //   messages.value.splice(-1, 1, aiMsg);
      //   agentRequestLoading.value = false;
      //   return;
      // }
      // aiMsg.content = value.data.answer;

      if (value.data == true) {
        console.log("完成", messages.value);
        agentRequestLoading.value = false;
        return;
      }

      aiMsg.content = value.data.answer;
      // aiMsg.content = replaceThinkToSection(value.data.answer);
      messages.value.splice(-1, 1, {
        role: "assistant",
        content: aiMsg.content,
      });
    }

    // const reader = response?.body?.getReader();
    // const decoder = new TextDecoder();

    // while (true) {
    //   const { done, value } = await reader.read();
    //   if (done) {
    //     console.log("完成了");
    //     messages.value.splice(-1, 1, aiMsg);
    //     break;
    //   }

    //   const chunk = decoder.decode(value);

    //   // 处理 SSE 格式数据（如 "data: {...}\n\n"）
    //   chunk.split("\n\n").forEach((part) => {
    //     if (part.startsWith("data:")) {
    //       const data = JSON.parse(part.replace("data:", "").trim());
    //       aiMsg.content += data.data.answer;
    //     }
    //   });
    // }
  } catch (err) {
    showError("网络错误", aiMsg);
  }
};

// 关闭 SSE 连接
const closeSSE = () => {
  if (eventSource) {
    eventSource.close();
    eventSource = null;
  }
};

function onSubmit(nextContent: string) {
  if (!nextContent) return;
  // onRequest(nextContent);
  // 如果是新会话先创建会话，再提问，否则直接提问
  if (sessionKey.value === newSessionStr) {
    createSession(nextContent);
  } else {
    connectSSE(nextContent);
  }

  content.value = "";
}

const onPromptsItemClick: PromptsProps["onItemClick"] = (info) => {
  onRequest(info.data.description as string);
};

// 新建会话
function onAddConversation() {
  // 检查是否存在新会话
  const findAdd = conversationsItems.value.some(
    (item) => item.key === newSessionStr
  );
  if (!findAdd) {
    conversationsItems.value = [
      {
        key: newSessionStr,
        label: newSessionStr,
        assistant_id: assistantInfo.value?.id,
      },
      ...conversationsItems.value,
    ];
    messages.value = [];
    sessionKey.value = newSessionStr;
  }
}

// 点击会话
const onConversationClick: ConversationsProps["onActiveChange"] = (key) => {
  messages.value = [];
  sessionKey.value = key;
  getSessionList(true);
};

const handleFileChange: AttachmentsProps["onChange"] = (info) =>
  (attachedFiles.value = info.fileList);

// ==================== Nodes ====================
const placeholderNode = computed(() => {
  return h(
    Space,
    { direction: "vertical", size: 16, style: styles.value.placeholder },
    [
      h(Welcome, {
        variant: "borderless",
        icon: h(Image, { src: aiIcon, width: "50px", height: "auto" }),
        title: "你好，我是你的工业智能助理",
        description: `${assistantInfo.value.description || "暂无描述"}`,
        extra: h(Space, {}, [
          h(Button, { icon: h(ShareAltOutlined) }),
          h(Button, { icon: h(EllipsisOutlined) }),
        ]),
      }),
      // h(Prompts, {
      //   title: "Do you want?",
      //   items: placeholderPromptsItems,
      //   styles: {
      //     list: {
      //       width: "100%",
      //     },
      //     item: {
      //       flex: 1,
      //     },
      //   },
      //   onItemClick: onPromptsItemClick,
      // }),
    ]
  );
});

const items = computed<BubbleListProps["items"]>(() => {
  if (messages.value.length === 0) {
    return [{ content: placeholderNode, variant: "borderless" }];
  }
  return messages.value.map((item) => {
    return {
      ...item,
      content: h("div", {
        innerHTML: md.render(replaceThinkToSection(item.content)),
      }),
      // content: replaceThinkToSection(item.content),
    };
  });
});

// 组件卸载时自动关闭连接
onBeforeUnmount(() => {
  closeSSE();
});
</script>

<style lang='scss' scoped>
:deep(.ant-bubble-content-borderless) {
  width: 100%;
  margin: 0 10px;
}
:deep(.ant-bubble) {
  margin: 0 10px;
}
:deep(.ant-conversations-item) {
  border: 1px solid rgba(112, 107, 107, 0.15);
  margin-bottom: 6px;
}
:deep(.think) {
  color: #8b8b8b;
  border-left: 2px solid #d5d3d3;
  padding-left: 10px;
  margin-bottom: 10px;
  font-size: 12px;
}
</style>